import {Injectable} from "@angular/core";
import {Router} from "@angular/router";
import {
  HttpClient,
  HttpErrorResponse,
  HttpHeaders,
  HttpResponse
} from "@angular/common/http";
import {BehaviorSubject, Observable, of, throwError} from "rxjs";
import {catchError, map} from "rxjs/operators";
import * as _ from "lodash";
import {NzMessageService} from "ng-zorro-antd/message";

const DEFAULT_CONFIG: FetchCallbackConfig = {
  responseData: "data",
  errorData: "error",
  showError: true
};

/**
 * 数据服务API访问封装服务，提供token注入、数据预处理、及错误预处理
 */
@Injectable()
export class FetchService {
  loadingStatusChange: BehaviorSubject<Set<string>> = new BehaviorSubject<Set<string>>(new Set());

  constructor(
    private http: HttpClient,
    private router: Router,
    private message: NzMessageService
  ) {
  }

  /**
   * 构建数据获取服务，预定义token封装，错误处理
   * @param url 请求地址
   * @param params 请求参数
   * @param fetchCallbackConfig 预处理相关配置。具体参见 {@link FetchCallbackConfig}
   * @returns
   */
  get<T>(
    url: string,
    params?: { [param: string]: any },
    fetchCallbackConfig?: FetchCallbackConfig
  ): Observable<T> {
    const uniqueKey = `${url}-${new Date().getTime()}`;
    this.pushLoadingQueue(uniqueKey);
    const config = _.defaultsDeep(
      fetchCallbackConfig,
      {showNotify: false},
      DEFAULT_CONFIG
    );
    return this.http.get(url, {params}).pipe(
      map<HttpResponse<T>, T>(response => {
        return this.handleResponse<T>(response, config, uniqueKey);
      }),
      catchError(responseError => {
        return throwError(
          this.handleError(responseError, config, uniqueKey)
        );
      })
    );
  }

  /**
   * POST请求服务
   * @param url 请求地址
   * @param data POST发送数据
   * @param fetchCallbackConfig 预处理相关配置。具体参见 {@link FetchCallbackConfig}
   * @returns
   */
  post<T>(
    url: string,
    data: object,
    fetchCallbackConfig?: FetchCallbackConfig
  ): Observable<T> {
    const uniqueKey = `${url}-${new Date().getTime()}`;
    this.pushLoadingQueue(uniqueKey);
    const config = _.defaultsDeep(
      fetchCallbackConfig,
      {showNotify: false},
      DEFAULT_CONFIG
    );
    return this.http
      .post(url, data, {
        headers: new HttpHeaders({...{}, ...config.headerOptions}) // (Object.assign({}, config.headerOptions))
      })
      .pipe(
        map<HttpResponse<T>, T>(response => {
          return this.handleResponse(response, config, uniqueKey);
        }),
        catchError(responseError => {
          return throwError(
            this.handleError(responseError, config, uniqueKey)
          );
        })
      );
  }

  export(
    url: string,
    data: object,
    fetchCallbackConfig?: FetchCallbackConfig
  ): Observable<any> {
    const uniqueKey = `${url}-${new Date().getTime()}`;
    this.pushLoadingQueue(uniqueKey);
    const config = _.defaultsDeep(
      fetchCallbackConfig,
      {showNotify: false},
      DEFAULT_CONFIG
    );
    return this.http
      .post(url, data, {
        responseType: 'arraybuffer',
        observe: "response",
        headers: new HttpHeaders({...{}, ...config.headerOptions})
      }).pipe( map<HttpResponse<any>, any>(response => response.body));
  }

  private handleResponse<T>(
    response: HttpResponse<T>,
    config: FetchCallbackConfig,
    uniqueKey: string
  ): T {
    this.shiftLoadingQueue(uniqueKey);
    if (response[config.errorData]) {
      this.handleError(response, config, uniqueKey);
      return response[config.errorData];
    } else {
      if (config.showNotify) {
        this.message.success(config.notifyMessage);
      }
      return response[config.responseData];
    }
  }

  private handleError(
    responseError,
    config: FetchCallbackConfig,
    uniqueKey: string
  ): { code: number; msg: string } {
    this.shiftLoadingQueue(uniqueKey);
    let error = {code: 400, msg: "未知错误"};
    if (responseError["body"] && responseError["body"][config.errorData]) {
      error = _.defaultsDeep(
        responseError["body"][config.errorData],
        error
      );
    } else if (responseError[config.errorData] && responseError[config.errorData].error) {
      error = _.defaultsDeep(
        responseError[config.errorData].error,
        error
      );
    } else if (responseError instanceof HttpErrorResponse) {
      this.router.navigate(["login"]);
      return;
      // error.code = responseError.status.toString();
      // error.msg = responseError.message;
    }
    if (error.code && error.code != 401) {
      if (config.showError) {
        const info = `错误代码：${error.code}<br/>错误信息：${error.msg}`;
        this.message.error(info);
      }
    } else {
      this.router.navigate(["login"]);
      return;
    }
    return error;
  }

  private pushLoadingQueue(uniqueKey: string): void {
    this.loadingStatusChange.value.add(uniqueKey);
    this.loadingStatusChange.next(this.loadingStatusChange.value);
  }

  private shiftLoadingQueue(uniqueKey: string): void {
    const loadingQueue = this.loadingStatusChange.value;
    if (loadingQueue.has(uniqueKey)) {
      loadingQueue.delete(uniqueKey);
      this.loadingStatusChange.next(loadingQueue);
    }
  }
}

/**
 * FetchService调用预处理、后处理相关设置
 */
export interface FetchCallbackConfig {
  /** response数据内容标签，默认为data */
  responseData?: string;
  /** error数据内容标签，默认为error */
  errorData?: string;
  /** 是否显示成功操作提示，默认为true */
  showNotify?: boolean;
  /** 是否显示操作失败提示，默认为true */
  showError?: boolean;
  /** 操作成功提示消息设置选项 */
  notifyMessage?: string;
  /** 操作失败提示消息设置选项 */
  errorNotifyMessage?: string;
  headerOptions?: { [name: string]: string | Array<string> };
}
